Skip to content

Stop registering IStudentResultsClient in the worker's dependency bundle - #328

Merged
ajs-uk-dev merged 3 commits into
mainfrom
326-worker-di-student-results-client
Aug 21, 2026
Merged

Stop registering IStudentResultsClient in the worker's dependency bundle#328
ajs-uk-dev merged 3 commits into
mainfrom
326-worker-di-student-results-client

Conversation

@dfe-lance

@dfe-lance dfe-lance commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Closes #326. Remedial work on AB#296648.

The rules-engine worker crash-loops at startup on current main — exit 139,
restarting continuously, no queue consumer and no retention job running.

Unhandled exception. System.AggregateException: Some services are not able to be
constructed (Error while validating the service descriptor 'ServiceType:
IStudentResultsClient Lifetime: Scoped ImplementationType: StudentResultsBlobClient':
Unable to resolve service for type 'Microsoft.Extensions.Caching.Memory.IMemoryCache'
while attempting to activate 'StudentResultsBlobClient'.)

Cause

8f89b358 added IStudentResultsClient to AddInfrastructureDependencies.
StudentResultsBlobClient takes an IMemoryCache, and the worker has none:
AddMemoryCache comes from AddPersistenceDependencies, which the worker
deliberately opts out of so its manual DbContext registration stays the single
source of truth. The container is validated on build, so the whole process dies
rather than one feature failing — the queue consumer, the dead-letter, metrics,
search-analytics and content-staging retention jobs all stop, over a service
none of them uses.

This is the same shape as #308: a registration added for the web's benefit
landing in a bundle the worker also executes.

Why removing it is safe

AddInfrastructureDependencies has one production caller — the worker — and it
never resolves IStudentResultsClient; there is no reference to the type
anywhere in that project. Its only other caller is AnalyticsRegistrationTests,
which resolves IAnalyticsService alone through an unvalidated provider.

Every consumer — JourneyController, ResultSuggestionsController,
DevDataSeedingOrchestrator and SeedStudentResults — is in the web host,
which does not call that bundle at all and registers the client itself in
AddCpdBlobStorage.

BlobStorageExtensionsTests was added under AB#296648 for the mirror image of
this failure: the client registered only in Infrastructure, so the web could not
boot. The web-side registration fixed that; the Infrastructure one was left
behind, and it is what now takes the worker down. That test still passes here,
including its IStudentResultsClient case, so the incorrect-grade enquiry
journey is untouched.

Guard

Removing one line fixes this instance. The test added alongside it covers the
class: it builds the bundle with ValidateOnBuild and fails if anything in it
cannot be constructed from what the worker provides, which is exactly what the
host does at startup and exactly what the worker died doing.

The Build workflow cannot catch this on its own — it compiles and runs tests and
never starts the worker host — so without a guard the failure reaches whoever
runs the container next, which is how it was found.

Writing the test also surfaced that the bundle's blob clients need a storage
connection string before BlobServiceClient is registered. Compose and every
deployed environment supply one, so the test supplies one too rather than
asserting against a container shape no host has.

Testing

Full Release build of the solution, clean with no new warnings. 4,123 unit and
653 integration tests pass on the branch merged with current main.

The guard was written before the fix and failed with the same IMemoryCache
error the container showed; restoring the registration turns it red again.

Verified in the container: the worker starts with 0 restarts and no unhandled
exception, /healthz/live and /healthz/ready both return 200, rules load and
the retention jobs run. The web app serves 200 throughout.

The rules-engine worker crash-looped at startup on main. StudentResultsBlobClient
takes an IMemoryCache, and the worker has none: AddMemoryCache comes from
AddPersistenceDependencies, which the worker deliberately opts out of so its
manual DbContext registration stays the single source of truth. The container is
validated on build, so the whole process died rather than one feature failing —
the queue consumer, the dead-letter, metrics, search-analytics and
content-staging retention jobs all stopped, over a service none of them uses.

The registration served no host. AddInfrastructureDependencies has one
production caller, the worker, which never resolves IStudentResultsClient. Every
consumer — JourneyController, ResultSuggestionsController, the dev-data seeding
orchestrator and SeedStudentResults — is in the web host, which does not call
that bundle at all and registers the client itself in AddCpdBlobStorage.

That web-side registration and its guarding test were added for the mirror image
of this failure: the client registered only in Infrastructure, so the web could
not boot. The web copy fixed that; this one was left behind, and it is what took
the worker down.

Added a guard for the class of failure rather than the instance. Constructing
every registration in the bundle is what the host does at startup and what the
worker died doing, so the test builds the bundle with validate-on-build and
fails if anything in it cannot be constructed from what the worker provides. The
Build workflow cannot catch this on its own — it compiles and runs tests, and
never starts the worker host, so the failure reaches whoever runs the container
next.

Verified by restoring the registration: the new test fails with the same
IMemoryCache error the container showed, and passes with it removed. The web
host's blob-client assertions, including the one covering IStudentResultsClient,
are unaffected.

Refs #326
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review app for PR 328 was deleted

ajs-uk-dev
ajs-uk-dev previously approved these changes Aug 20, 2026
EditPage_PrefillsCurrentValue read the value the window Summary displays and
asserted the edit field held the same string. "Not set" is what the Summary
prints when there is no commitment — a placeholder, not the value — so once
EmptySubmission_IsAllowed_AndShowsNotSet had cleared the commitment, the test
compared "Not set" against a legitimately empty input and failed:

  Locator expected to have value 'Not set'
    - unexpected value ""

xUnit does not order tests within a class, so whichever order a run happened to
pick decided whether this passed. It passes locally and failed the E2E job,
which is the same test on the same commit.

Mapping the placeholder to the empty string makes the assertion hold in both
states, which is what its comment already claimed. Reproduced by running
EmptySubmission first and then the prefill test — failing before the change with
the error above, passing after — and confirmed the non-empty path still asserts
by running Save_PersistsValue first and checking the prefill matches the value
it saved.

AB#296637
@ajs-uk-dev
ajs-uk-dev merged commit 15f9dfd into main Aug 21, 2026
8 checks passed
@dfe-lance
dfe-lance deleted the 326-worker-di-student-results-client branch August 21, 2026 15:59
ajs-uk-dev pushed a commit that referenced this pull request Aug 22, 2026
The comment claimed AddInfrastructureDependencies registers
StudentResultsBlobClient, but #328 moved that registration to the web host's
AddCpdBlobStorage. Nothing left in the worker's service graph consumes
IMemoryCache, and the worker validates on build, so a missed consumer would
fail its startup rather than pass silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rules-engine worker crash-loops: IStudentResultsClient registered in a bundle without IMemoryCache

4 participants